<?php
/**
 * Created by PhpStorm.
 * User: DELL-PC
 * Date: 2018/8/18
 * Time: 16:50
 */

namespace J\Schema\Collection\Element;


use J\Schema\IElement;
use J\Schema\Collection\Collection;
use J\Schema\Sprite;
use J\Schema\Syntax;
use J\Exception;
use J\Manager;
use J\Message;
use J\ReadOnly;

class ElementManager extends Manager
{
    /**
     * @var Collection
     */
    protected $poem;

    /**
     * @var ReadOnly
     */
    protected $config;

    /**
     * @var string[] 域名映射
     */
    protected $fields;

    /**
     * @var string[] 列名映射
     */
    protected $columns;

    /**
     * @var ElementFactory
     */
    protected $factory;

    /**
     * ElementManager constructor.
     * @param Collection $poem
     * @param bool $inner
     * @throws Exception
     */
    public function __construct(Collection $poem, $inner = false)
    {
        $this->poem = $poem;

        $config = $poem->getAttribute()->get(Syntax::KEY_ELEMENTS);

        // 不能为null， null意为删除

        // 普通表增加status项
        if (!$inner) $config[Syntax::KEY_STATUS] = [];
        $this->config = new ReadOnly($config);

        // 增加ID项
        $this->getObject(Syntax::KEY_ID);

        if (!$inner) $this->getObject(Syntax::KEY_STATUS);

        $this->iterateElements(function (IElement $element){
            $this->fields[$element->getFieldName()] = $element->getName();
            $this->columns[$element->getColumnName()] = $element->getName();
        });
    }

    /**
     * 判断element名称有效性
     *
     * @param string $name
     * @return boolean
     */
    public function hasElement($name)
    {
        return $this->has($name) or $this->config->has(strtolower($name));
    }

    /**
     * 遍历元素
     *
     * @param \Closure $closure
     *      function(IElement element, value) : false|true|null
     *          false: 直接退出遍历
     * @param string|string[] $elements
     * @param \Closure $error
     *      function(IElement element, value) : false|true|null
     *          false: 直接退出遍历
     * @throws Exception
     */
    public function iterateElements(\Closure $closure, $elements = Syntax::KEY_ALL, $error = null)
    {
        if ($elements === Syntax::KEY_ALL) $elements = $this->config->getKeys();

        if (is_string($elements)) $elements = [$elements => null];

        foreach ($elements as $key => $value) {
            $name = $this->findElementName($value, $key);
            // 错误处理
            if (!$this->hasElement($name)) {
                if ($error and $error($name, $value) === false) return;
                continue;
            }
            //
            $element = $this->getElement($name);

            if (false === $closure($element, $value)) return;
        }
    }

    /**
     * 获取元素对象
     *
     * @param string $name
     * @return IElement
     * @throws Exception
     */
    public function getElement($name)
    {
        if (!$this->hasElement($name))
            throw new Exception(Message::ELEMENT_NOT_FIND, ['element' => $name]);
        return $this->getObject($name);
    }

    /**
     * 通过域名获取元素名
     *
     * @param string $name
     * @return IElement
     * @throws Exception
     */
    public function getElementByFieldName($name)
    {
        if (!isset($this->fields[$name]))
            throw new Exception(Message::INVALID_FIELD_NAME, ['field' => $name]);
        return $this->getElement($this->fields[$name]);
    }

    /**
     * 通过列名获取元素名
     *
     * @param string $name
     * @return IElement
     * @throws Exception
     */
    public function getElementByColumnName($name)
    {
        if (!isset($this->columns[$name]))
            throw new Exception(Message::INVALID_COLUMN_NAME, ['column' => $name]);
        return $this->getElement($this->columns[$name]);
    }

    /**
     * 解析参数
     *
     * @param mixed $value
     * @param string $name
     * @return string
     */
    public function findElementName(&$value, $name = null)
    {
        if (!empty($name) and is_string($name)) return $name;

        if (is_string($value)) {
            $name = $value;
            $value = null;
            return $name;
        }

        if ($value instanceof Sprite) return $value->getName();

        return array_isset($value, 'name', array_isset($value, 0));
    }

    /**
     *
     * @param string $name
     *
     * @return IElement
     */
    protected function createObject($name)
    {
        $key = lcfirst($name);
        $schema = $this->config->get($key);
        return $this->getElementFactory()->create($this->poem, $name, $schema);
    }

    public function getElementFactory()
    {
        if (!$this->factory) {
            try {
                $this->factory = app()->Element();
            } catch (\Throwable $e) {
                $this->factory = new ElementFactory();
                $this->factory->setConfig(app()->get('Element'));
            }
        }

        return $this->factory;
    }
}